#include "serial_port.h"
#include "serial_data.h"
#include "serial_help_fun.h"
#include <getopt.h>



int speed_arr[] = {
	B4000000,
	B3000000,
	B2500000,
	B2000000,
	B1000000,
	B921600,
	B460800,
	B230400,
	B115200,
	B57600,
	B38400,
	B19200,
	B9600,
	B4800,
	B2400,
	B1200,
	B600,
	B300,
	B200,
	B150,
	B110,
	B75,
	B50
};
 
int name_arr[] = {
	4000000,
	3000000,
	2500000,
	2000000,
	1000000,
	921600,
	460800,
	230400,
	115200,
	57600,
	38400,
	19200,
	9600,
	4800,
	2400,
	1200,
	600,
	300,
	200,
	150,
	110,
	75,
	50
};
 

static void display_help(void)
{
	printf("Usage: linux-serial-test [OPTION]\n"
			"\n"
			"  -h, --help\n"
			"  -b, --baud         Baud rate, 115200, etc (115200 is default)\n"
			"  -p, --port         Port (/dev/ttyS0, etc) (must be specified)\n"
			"  -r, --rx           Rx data\n"
			"  -w, --tx-bytes     Number of bytes for each write (default is to repeatedly write 1024 bytes\n"

			"\n"
	      );
}

static void print_usage(const char *prog)
{
	printf("Usage: %s [-bprw]\n", prog);
    display_help();
	// puts("  -D --device    tty device to use\n"
	// 	 "  -S --speed     uart speed\n"
	// 	 "  -v --verbose   Verbose (show rx buffer)\n"
	// 	 "  -f --hardflow  open hardware flowcontrol\n"
	// 	 "  -R --rs485     enable rs485 function\n"
	// 	 "  -s --savefile  save rx data to file\n");
	exit(1);
}


void serial_conf_print(serial_cfg_t conf)
{
		printf("serial_conf_print: \n"
			"\n"
			"[baudrate:%d, databits:%d,  stopbits:%d, parity:%c, hardflow:%d]"

			"\n",conf.baudrate, conf.databits, conf.stopbits, conf.parity, conf.hardflow
	      );
}


/**
 ****************************************************************************************
 * @brief       
 * @param [in]  
		  [out] 
 * @return      
 * @note        
****************************************************************************************
 */
void serial_process_options(int argc, char * argv[], serial_cfg_t *conf, char *devname)
{
	for (;;) {
		int option_index = 0;
		static const char *short_options = "hb:p:r:w:";
		static const struct option long_options[] = {
			{"help", no_argument, 0, 0},
			{"baud", required_argument, 0, 'b'},
			{"port", required_argument, 0, 'p'},
			{"rx-bytes", required_argument, 0, 'r'},
			{"tx-bytes", required_argument, 0, 'w'},
			{0,0,0,0},
		};

		int c = getopt_long(argc, argv, short_options,
				long_options, &option_index);

		if (c == EOF) {
			break;
		}

		switch (c) {
		case 0:
		case 'h':
			display_help();
			exit(0);
			break;
		case 'b':
			conf->baudrate = atoi(optarg);
			break;
		case 'p':
			//devname = strdup(optarg);
			memcpy(devname, strdup(optarg), strlen(strdup(optarg)));
			printf("devname:%s\n",devname);
			break;
		case 'r': {
			// char *endptr;
			// s_tx_bytes = strtol(optarg, &endptr, 0);
            // s_role = ROLE_READ;
			break;
		}
		case 'w': {
			char *endptr;
			// s_tx_bytes = strtol(optarg, &endptr, 0);
            // s_role = ROLE_WRITE;
			break;
		}
        default:
            print_usage(argv[0]);
            break;
		}
	}
}



/**
 ****************************************************************************************
 * @brief       open serial devices
 * @param [in]  devname: the device name to open
          [out]  
 * @return      fd: the file discriptor
 * @note        In this demo device is opened blocked, you could modify it at will.
****************************************************************************************
 */
static int serial_open(const char *devname)
{
	int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); 
	int flags = 0;
	
	if (fd < 0) {                        
		perror("open device failed");
		return -1;            
	}
	
	flags = fcntl(fd, F_GETFL, 0);
	flags &= ~O_NONBLOCK;
	if (fcntl(fd, F_SETFL, flags) < 0) {
		printf("fcntl failed.\n");
		return -1;
	}
		
	if (isatty(fd) == 0) {
		printf("not tty device.\n");
		return -1;
	}
	else
		printf("tty device test ok.\n");
	
	return fd;
}

/**
 * serial_setcfg - config tty device
 * @fd: device handle
 * @serial_cfg_t: config to set
 *
 * The function return 0 if success, or -1 if fail.
 */
/**
 ****************************************************************************************
 * @brief       config serial device
 * @param [in]  fd: file descriptor
 *              serial_cfg_t: serial config struct    
          [out] 
 * @return      
 * @note        
****************************************************************************************
 */

// typedef struct serial_cfg_t{
//     int baudrate;
//     int databits;
//     int stopbits;
//     int parity;
// } serial_cfg_t;



static int serial_setcfg(int fd, serial_cfg_t conf)
{
	struct termios newtio;
	struct termios oldtio;
	int speed_flag = -1;
	int i;
	
	bzero(&newtio, sizeof(newtio));
	bzero(&oldtio, sizeof(oldtio));
	
	if (tcgetattr(fd, &oldtio) != 0) {
		perror("tcgetattr");    
		return -1; 
	}
	newtio.c_cflag |= CLOCAL | CREAD;
	newtio.c_cflag &= ~CSIZE;
 
	/* set tty speed */
	for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
		if (conf.baudrate == name_arr[i]) {   
			speed_flag = 1;   
			cfsetispeed(&newtio, speed_arr[i]); 
			cfsetospeed(&newtio, speed_arr[i]);   
		} 
	}
	
	if(speed_flag != 1)
	{
		fprintf(stderr, "unsupported serial baudrate:%d\n", conf.baudrate);
		return -1; 
	}
   
    // cfsetispeed(&newtio, conf.baudrate); 
    // cfsetospeed(&newtio, conf.baudrate);   

	
	/* set data bits */
	switch (conf.databits) {
	case 5:                
		newtio.c_cflag |= CS5;
		break;
	case 6:                
		newtio.c_cflag |= CS6;
		break;
	case 7:                
		newtio.c_cflag |= CS7;
		break;
	case 8:    
		newtio.c_cflag |= CS8;
		break;  
	default:   
		fprintf(stderr, "unsupported data size\n");
		return -1; 
	}
	
	/* set parity */
	switch (conf.parity) {  
	case 'n':
	case 'N':
		newtio.c_cflag &= ~PARENB;    /* Clear parity enable */
		newtio.c_iflag &= ~INPCK;     /* Disable input parity check */
		break; 
	case 'o':  
	case 'O':    
		newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */
		newtio.c_iflag |= INPCK;     /* Enable input parity check */
		break; 
	case 'e': 
	case 'E':  
		newtio.c_cflag |= PARENB;    /* Enable parity */   
		newtio.c_cflag &= ~PARODD;   /* Even parity instead of odd */  
		newtio.c_iflag |= INPCK;     /* Enable input parity check */
		break;
	default:  
		fprintf(stderr, "unsupported parity\n");
		return -1; 
	} 
	
	/* set stop bits */ 
	switch (conf.stopbits) {  
	case 1:   
		newtio.c_cflag &= ~CSTOPB; 
		break;
	case 2:   
		newtio.c_cflag |= CSTOPB; 
		break;
	default:   
		perror("unsupported stop bits\n"); 
		return -1;
	}
 
	if (conf.hardflow)
		newtio.c_cflag |= CRTSCTS;
	else
		newtio.c_cflag &= ~CRTSCTS;
 
	newtio.c_cc[VTIME] = 20;	/* Time-out value (tenths of a second) [!ICANON]. */
	newtio.c_cc[VMIN] = 256;    /* Minimum number of bytes read at once [!ICANON]. */
	
	tcflush(fd, TCIOFLUSH);  
	
	if (tcsetattr(fd, TCSANOW, &newtio) != 0) {
		perror("tcsetattr");
		return -1;
	}
	return 0;
}


/**
 ****************************************************************************************
 * @brief       
 * @param [in]  
		  [out] 
 * @return      
 * @note        
****************************************************************************************
 */
int serial_port_init(int *fd, const char *devname,  const serial_cfg_t *conf, RingBuffer *recv_data)
{
	int ret;
	*fd =  serial_open(devname);
	ret = serial_setcfg(*fd, *conf);

	if(ret <0)
    {
        perror("serial_setcfg fail.\n");
        return -1;
    }

	user_buffer_create(recv_data, RECV_BUFFER_SIZE + RECV_BUFFER_WATERMARK_SIZE, RECV_BUFFER_SIZE, RECV_BUFFER_SIZE -RECV_BUFFER_WATERMARK_SIZE);

	return 0;
}


/**
 ****************************************************************************************
 * @brief       
 * @param [in]  
		  [out] 
 * @return      
 * @note        
****************************************************************************************
 */
int serial_read(int fd, RingBuffer *recv_buffer, int recv_len)
{
	unsigned char buf[RECV_BUFFER_SIZE];
	if(recv_len > RECV_BUFFER_SIZE)
	{
		recv_len = RECV_BUFFER_SIZE;
	}

	int len = read(fd, buf, recv_len);
	if(len >0)
	{
		user_buffer_write_items(recv_buffer,buf,len);
	}
	
	return len;
}

/**
 ****************************************************************************************
 * @brief       
 * @param [in]  
		  [out] 
 * @return      
 * @note        
****************************************************************************************
 */

int serial_write(int fd,  const uint8_t *buf, int len)
{
	return write(fd, buf, len);
}

/**
 ****************************************************************************************
 * @brief       
 * @param [in]  
          [out] 
 * @return      
 * @note        
****************************************************************************************
 */
int serial_close(int fd)
{
	if(fd >0)
		return close(fd);
	return -1;
}